/*
 * BlackBox.java
 *
 * Created on March 10, 2008, 6:50 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */
package hextd.blackBox;

import hextd.HexTD;
import hextd.hex.Hex;
import hextd.tower.Tower;
import hextd.tower.TowerFactory;
import hextd.util.Context;
import hextd.util.MapIO;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Iterator;
import java.util.Vector;

/**
 *
 * @author hylke
 */
public class BlackBox {

    private Context context;
    private HexTD mainGame;
    private Vector<BBAction> actions;
    private int replayPointer = 0;
    private Vector<BBActionListener> actionListeners;

    public static enum ActionType {

        MapStart('L'),
        End('E'),
        NextWave('W'),
        NewTower('N'),
        UpgradeTower('U'),
        SellTower('S'),
        SetMode('M');
        public final char key;

        ActionType(char key) {
            this.key = key;
        }
    }

    /** Creates a new instance of BlackBox */
    public BlackBox(Context c, HexTD h) {
        this.context = c;
        this.mainGame = h;
        this.actions = new Vector<BBAction>();
        this.actionListeners = new Vector<BBActionListener>();
    }

    public void clear() {
        synchronized (context.gameTimeLock) {
            this.actions.clear();
            this.context.gameTime = 0;
            this.replayPointer = 0;
            System.out.println("BlackBox::clear: Cleared");
        }
    }

    public BlackBox copy() {
        BlackBox tempBox = new BlackBox(this.context, this.mainGame);
        BBAction[] actionArray = new BBAction[this.actions.size()];
        tempBox.actions.addAll(this.actions);
        return tempBox;
    }

    public void copyFrom(BlackBox source) {
        synchronized (context.gameTimeLock) {
            this.clear();
            Vector<BBAction> sourceActions = source.getActions();
            this.actions.addAll(sourceActions);
        }
        this.fireActionCountChanged();
    }

    public void rewind() {
        this.replayPointer = 0;
    }

    public void endRecording() {
        synchronized (context.gameTimeLock) {
            BBAction newAction = new BBActionEnd(this.context.gameTime);
            this.actions.add(newAction);
        }
    }

    public int getNextActionTime() {
        if (this.replayPointer < this.actions.size()) {
            return this.actions.get(this.replayPointer).getGameTime();
        }
        return -1;
    }

    public StringBuilder printLog() {
        BBAction a;
        StringBuilder log = new StringBuilder();
        for (Iterator<BBAction> i = this.actions.iterator(); i.hasNext();) {
            a = i.next();
            log.append(a.getString());
        }
        return log;
    }

    public Vector<BBAction> getActions() {
        return actions;
    }

    public int getActionCount() {
        return this.actions.size();
    }

    public int getActionPointer() {
        return this.replayPointer + 1;
    }

    public int getNextWaveActionCount() {
        int count = 0;
        for (int i = this.actions.size() - 1; i >= 0; i--) {
            if (this.actions.get(i).getType() == BlackBox.ActionType.NextWave) {
                count++;
            }
        }
        return count;
    }

    public boolean loadLog(BufferedReader logIn) {
        String line;
        String[] parts;
        int gameTime;
        int lineCounter = 0;
        BBAction newAction;

        this.clear();

        try {
            while ((line = logIn.readLine()) != null) {
                lineCounter++;
                parts = line.trim().split(" ");
                try {
                    gameTime = new Integer(parts[0]).intValue();
                } catch (NumberFormatException ex) {
                    System.out.println("BlackBox::loadLog: Ignoring line: " + line);
                    continue;
                }
                switch (parts[1].charAt(0)) {
                    case 'L':
                        // int gameTime, String map, String mapMd5, String version
                        String mapMD5 = "";
                        String version = "";
                        if (parts.length >= 5) {
                            mapMD5 = parts[3];
                            version = parts[4];
                        }
                        newAction = new BBActionMapStart(gameTime, parts[2], mapMD5, version);
                        this.actions.add(newAction);
                        break;

                    case 'W':
                        newAction = new BBActionNextWave(gameTime);
                        this.actions.add(newAction);
                        break;

                    case 'N':
                        TowerFactory.type type;

                        try {
                            int row;
                            int col;
                            row = new Integer(parts[2]).intValue();
                            col = new Integer(parts[3]).intValue();
                            type = TowerFactory.table.get(parts[4]);
                            newAction = new BBActionNewTower(gameTime, row, col, type);
                            this.actions.add(newAction);
                        } catch (NumberFormatException ex) {
                            ex.printStackTrace();
                        }
                        break;

                    case 'M':
                        try {
                            int row;
                            int col;
                            int mode;
                            int value;
                            row = new Integer(parts[2]).intValue();
                            col = new Integer(parts[3]).intValue();
                            mode = new Integer(parts[4]).intValue();
                            value = new Integer(parts[5]).intValue();
                            newAction = new BBActionSetMode(gameTime, row, col, mode, value);
                            this.actions.add(newAction);
                        } catch (NumberFormatException ex) {
                            ex.printStackTrace();
                        }
                        break;

                    case 'U':
                        try {
                            int row;
                            int col;
                            row = new Integer(parts[2]).intValue();
                            col = new Integer(parts[3]).intValue();
                            newAction = new BBActionUpgradeTower(gameTime, row, col);
                            this.actions.add(newAction);
                        } catch (NumberFormatException ex) {
                            ex.printStackTrace();
                        }
                        break;
                    case 'S':
                        try {
                            int row;
                            int col;
                            row = new Integer(parts[2]).intValue();
                            col = new Integer(parts[3]).intValue();
                            newAction = new BBActionSellTower(gameTime, row, col);
                            this.actions.add(newAction);
                        } catch (NumberFormatException ex) {
                            ex.printStackTrace();
                        }
                        break;
                    case 'E':
                        newAction = new BBActionEnd(gameTime);
                        this.actions.add(newAction);
                        break;
                    default:
                        System.err.println("BlackBox::loadLog: Unknown action: " + parts[1]);
                        break;
                }

            }
        } catch (IOException ex) {
            ex.printStackTrace();
            return false;
        }
        System.out.println("BlackBox::loadLog: loaded "+this.actions.size()+" actions from "+lineCounter+" lines.");
        this.fireActionCountChanged();
        return true;
    }

    public void doActions() {
        int gameTime = this.context.gameTime;
        if (this.replayPointer < this.actions.size()) {
            BBAction nextAction = this.actions.get(this.replayPointer);
            while ((this.replayPointer < this.actions.size()) && (nextAction.getGameTime() == gameTime)) {
                nextAction.execute(this.context);
                this.fireActionExecutedEvent(nextAction);
                this.replayPointer++;
                if (this.replayPointer < this.actions.size()) {
                    nextAction = this.actions.get(this.replayPointer);
                }
            }
        }
    }

    public boolean newTower(int col, int row, TowerFactory.type t) {
        boolean success = false;
        synchronized (context.gameTimeLock) {
            Hex hex = this.context.getHex(col, row);
            if (hex.isFree()) {
                if (this.context.doPay(t.price)) {
                    // Place the tower
                    System.out.println("BlackBox::newTower: " + context.gameTime + " Building new tower of type " + t.toString() + " at " + row + "," + col);
                    Tower tempTower = TowerFactory.createTower(t, this.context, hex);
                    this.context.addTower(tempTower);
                    hex.setTower(tempTower);

                    // Record it
                    BBAction newAction = new BBActionNewTower(this.context.gameTime, row, col, t);
                    this.actions.add(newAction);
                    success = true;
                }
            }
        }
        this.fireActionCountChanged();
        return success;
    }

    public boolean sellTower(int col, int row) {
        boolean success = false;
        synchronized (context.gameTimeLock) {
            Hex hex = this.context.getHex(col, row);
            if (hex.hasTower()) {
                System.out.println("BlackBox::sellTower: " + context.gameTime + " Selling tower at " + row + "," + col);
                Tower t = hex.getTower();
                hex.unSetTower();
                t.doCleanup();
                this.context.doReceive(t.getSellPrice());
                this.context.removeTower(t);

                // Record it
                BBAction newAction = new BBActionSellTower(this.context.gameTime, row, col);
                this.actions.add(newAction);
                success = true;
            }
        }
        this.fireActionCountChanged();
        return success;
    }

    public boolean upgradeTower(int col, int row) {
        boolean success = false;
        synchronized (context.gameTimeLock) {
            Hex hex = this.context.getHex(col, row);
            if (hex.hasTower()) {
                Tower t = hex.getTower();
                if (this.context.doPay(t.getUpgradePrice())) {
                    System.out.println("BlackBox::upgradeTower: " + context.gameTime + " Upgrading tower at " + row + "," + col);
                    t.doUpgrade();

                    // Record it
                    BBAction newAction = new BBActionUpgradeTower(this.context.gameTime, row, col);
                    this.actions.add(newAction);
                    success = true;
                }
            }
        }
        this.fireActionCountChanged();
        return success;
    }

    public boolean nextWave() {
        boolean success = false;
        synchronized (context.gameTimeLock) {
            if (this.context.getEnemyCount() == 0) {
                System.out.println("BlackBox::nextWave: " + this.context.gameTime + " Sending next Wave.");
                this.context.nextWave();
                // Record it
                BBAction newAction = new BBActionNextWave(this.context.gameTime);
                this.actions.add(newAction);
                success = true;
            }
        }
        this.fireActionCountChanged();
        return success;
    }

    public boolean setMode(int col, int row, int m, int v) {
        boolean success = false;
        synchronized (context.gameTimeLock) {
            Hex hex = this.context.getHex(col, row);
            if (hex.hasTower()) {
                Tower t = hex.getTower();
                if (t.setMode(m, v)) {
                    System.out.println("BlackBox::setMode: " + this.context.gameTime + " Setting mode "+m+" to "+v+" on tower at " + row + "," + col);
                    // Record it
                    BBAction newAction = new BBActionSetMode(this.context.gameTime, row, col, m, v);
                    this.actions.add(newAction);
                    success = true;
                }
            }
        }
        this.fireActionCountChanged();
        return success;
    }

    public boolean startMap(String map) {
        boolean success = false;
        synchronized (context.gameTimeLock) {
            System.out.println("BlackBox::startMap: " + context.gameTime + " Starting map " + map);
            if (map.equalsIgnoreCase("Custom")) {
                System.out.println("Replay of custom map, cannot make sure it is loaded correctly during replay.");
                this.context.mapMD5 = MapIO.generateMapMD5(this.context);
                success = true;
            } else {
                success = this.context.loadMap(map);
            }

            if (success) {
                // We're starting a map, thus clear this blackbox for recording
                this.clear();
                // Record it
                BBAction newAction = new BBActionMapStart(this.context.gameTime, map, this.context.mapMD5, this.context.VERSION);
                this.actions.add(newAction);
            }

            this.context.resetMap();
        }
        this.fireActionCountChanged();
        return success;
    }

    public boolean endGame() {
        synchronized (context.gameTimeLock) {
            System.out.println("BlackBox::endGame: " + context.gameTime + " Ending game");
            this.mainGame.togglePause();
            BBAction newAction = new BBActionEnd(this.context.gameTime);
            this.actions.add(newAction);
        }
        this.fireActionCountChanged();
        return true;
    }

    public void addBBActionListener(BBActionListener l) {
        this.actionListeners.add(l);
    }

    public void removeBBActionListener(BBActionListener l) {
        this.actionListeners.remove(l);
    }

    private void fireActionExecutedEvent(BBAction a) {
        for (int i = 0; i < this.actionListeners.size(); i++) {
            this.actionListeners.get(i).ActionExecuted(a);
        }
    }

    private void fireActionCountChanged() {
        for (int i = 0; i < this.actionListeners.size(); i++) {
            this.actionListeners.get(i).ActionCountChanged();
        }
    }
}
